//
// detail/impl/service_registry.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_DETAIL_IMPL_SERVICE_REGISTRY_IPP
#define ASIO_DETAIL_IMPL_SERVICE_REGISTRY_IPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "asio/detail/config.hpp"
#include <vector>
#include "asio/detail/service_registry.hpp"
#include "asio/detail/throw_exception.hpp"

#include "asio/detail/push_options.hpp"

namespace asio {
    namespace detail {

        service_registry::service_registry(execution_context &owner)
                : owner_(owner),
                  first_service_(0) {
        }

        service_registry::~service_registry() {
        }

        void service_registry::shutdown_services() {
            execution_context::service *service = first_service_;
            while (service) {
                service->shutdown();
                service = service->next_;
            }
        }

        void service_registry::destroy_services() {
            while (first_service_) {
                execution_context::service *next_service = first_service_->next_;
                destroy(first_service_);
                first_service_ = next_service;
            }
        }

        void service_registry::notify_fork(execution_context::fork_event fork_ev) {
            // Make a copy of all of the services while holding the lock. We don't want
            // to hold the lock while calling into each service, as it may try to call
            // back into this class.
            std::vector < execution_context::service * > services;
            {
                asio::detail::mutex::scoped_lock lock(mutex_);
                execution_context::service *service = first_service_;
                while (service) {
                    services.push_back(service);
                    service = service->next_;
                }
            }

            // If processing the fork_prepare event, we want to go in reverse order of
            // service registration, which happens to be the existing order of the
            // services in the vector. For the other events we want to go in the other
            // direction.
            std::size_t num_services = services.size();
            if (fork_ev == execution_context::fork_prepare)
                for (std::size_t i = 0; i < num_services; ++i)
                    services[i]->notify_fork(fork_ev);
            else
                for (std::size_t i = num_services; i > 0; --i)
                    services[i - 1]->notify_fork(fork_ev);
        }

        void service_registry::init_key_from_id(execution_context::service::key &key,
                                                const execution_context::id &id) {
            key.type_info_ = 0;
            key.id_ = &id;
        }

        bool service_registry::keys_match(
                const execution_context::service::key &key1,
                const execution_context::service::key &key2) {
            if (key1.id_ && key2.id_)
                if (key1.id_ == key2.id_)
                    return true;
            if (key1.type_info_ && key2.type_info_)
                if (*key1.type_info_ == *key2.type_info_)
                    return true;
            return false;
        }

        void service_registry::destroy(execution_context::service *service) {
            delete service;
        }

        execution_context::service *service_registry::do_use_service(
                const execution_context::service::key &key,
                factory_type factory, void *owner) {
            asio::detail::mutex::scoped_lock lock(mutex_);

            // First see if there is an existing service object with the given key.
            execution_context::service *service = first_service_;
            while (service) {
                if (keys_match(service->key_, key))
                    return service;
                service = service->next_;
            }

            // Create a new service object. The service registry's mutex is not locked
            // at this time to allow for nested calls into this function from the new
            // service's constructor.
            lock.unlock();
            auto_service_ptr new_service = {factory(owner)};
            new_service.ptr_->key_ = key;
            lock.lock();

            // Check that nobody else created another service object of the same type
            // while the lock was released.
            service = first_service_;
            while (service) {
                if (keys_match(service->key_, key))
                    return service;
                service = service->next_;
            }

            // Service was successfully initialised, pass ownership to registry.
            new_service.ptr_->next_ = first_service_;
            first_service_ = new_service.ptr_;
            new_service.ptr_ = 0;
            return first_service_;
        }

        void service_registry::do_add_service(
                const execution_context::service::key &key,
                execution_context::service *new_service) {
            if (&owner_ != &new_service->context())
                asio::detail::throw_exception(invalid_service_owner());

            asio::detail::mutex::scoped_lock lock(mutex_);

            // Check if there is an existing service object with the given key.
            execution_context::service *service = first_service_;
            while (service) {
                if (keys_match(service->key_, key))
                    asio::detail::throw_exception(service_already_exists());
                service = service->next_;
            }

            // Take ownership of the service object.
            new_service->key_ = key;
            new_service->next_ = first_service_;
            first_service_ = new_service;
        }

        bool service_registry::do_has_service(
                const execution_context::service::key &key) const {
            asio::detail::mutex::scoped_lock lock(mutex_);

            execution_context::service *service = first_service_;
            while (service) {
                if (keys_match(service->key_, key))
                    return true;
                service = service->next_;
            }

            return false;
        }

    } // namespace detail
} // namespace asio

#include "asio/detail/pop_options.hpp"

#endif // ASIO_DETAIL_IMPL_SERVICE_REGISTRY_IPP
